/***************************************************************************
 * UTILITIES.CPP
 *
 * Utility Functions: Global to Application.
 *
 *  Copyright (c)1999-2002 Microsoft Corporation, All Rights Reserved
 *  Written by DSOI Office Integration, Microsoft Developer Support
 *
 *  THIS CODE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
 *  EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 *  WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 ***************************************************************************/
#include "owebfolder.h"

///////////////////////////////////////////////////////////////////////////////////
// Win32 Unicode API wrappers
//
//  This project is compiled using ANSI in order for it to run on Win9x machines 
//  that don't support Unicode. However, in order to try to keep the code 
//  language/locale neutral we need to use Unicode in places where we pass file 
//  names and paths. So we are using these wrappers to convert Unicode file info 
//  to ANSI only when needed (i.e., running on Win9x).
//
typedef HANDLE (WINAPI *PFN_CREATEFILEW)(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE);
typedef INT    (WINAPI *PFN_CMPSTRINGW)(LCID, DWORD, LPCWSTR, INT, LPCWSTR, INT);
typedef BOOL   (WINAPI *PFN_OPENFILENAME)(OPENFILENAMEW*);
typedef BOOL   (WINAPI *PFN_SAVEFILENAME)(OPENFILENAMEW*);

static BOOL              v_bRunningOnWin9x     = FALSE;
static PFN_CREATEFILEW   v_pfnCreateFileW      = NULL;
static PFN_CMPSTRINGW    v_pfnCompareStringW   = NULL;
static PFN_OPENFILENAME  v_pfnGetOpenFileNameW = NULL;
static PFN_SAVEFILENAME  v_pfnGetSaveFileNameW = NULL;

///////////////////////////////////////////////////////////////////////////////////
// FEnsureProcAddresses
//
//  Gets the proc addresses we'll use on Unicode OS (Windows NT, 2000, XP).
//
static BOOL __fastcall FEnsureProcAddresses()
{
    HMODULE hDll;

 // Check for Win9x system...
    v_bRunningOnWin9x = (GetVersion() & 0x80000000);
	if (v_bRunningOnWin9x) return FALSE;

 // Get function pointers for kernel32...
    if ((v_pfnCreateFileW == NULL) || (v_pfnCompareStringW == NULL))
    {
        hDll = GetModuleHandle("kernel32.dll");
		if (hDll)
		{
			v_pfnCreateFileW = (PFN_CREATEFILEW)GetProcAddress(hDll, "CreateFileW");
			v_pfnCompareStringW = (PFN_CMPSTRINGW)GetProcAddress(hDll, "CompareStringW");
		}
	}

 // Get function pointers for comdlg32...
    if ((v_pfnGetOpenFileNameW == NULL) || (v_pfnGetSaveFileNameW == NULL))
    {
        hDll = GetModuleHandle("comdlg32.dll");
		if (hDll)
		{
			v_pfnGetOpenFileNameW = (PFN_OPENFILENAME)GetProcAddress(hDll, "GetOpenFileNameW");
			v_pfnGetSaveFileNameW = (PFN_SAVEFILENAME)GetProcAddress(hDll, "GetSaveFileNameW");
		}
	}

    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////////
// Global String Functions
//
//  DsoConvertToUnicodeEx and DsoConvertToMBCSEx are simple wrappers around 
//  MultiByteToWideChar and WideCharToMultiByte (so we can add extra code page
//  support at a later time).
//
STDAPI DsoConvertToUnicodeEx(LPCSTR pszMbcsString, DWORD cbMbcsLen, LPWSTR pwszUnicode, DWORD cbUniLen)
{
	DWORD cbRet;

	CHECK_NULL_RETURN(pwszUnicode,    E_POINTER);
	pwszUnicode[0] = L'\0';

	CHECK_NULL_RETURN(pszMbcsString,  E_POINTER);
	CHECK_NULL_RETURN(cbMbcsLen,      E_INVALIDARG);
	CHECK_NULL_RETURN(cbUniLen,       E_INVALIDARG);

	cbRet = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, pszMbcsString, cbMbcsLen, pwszUnicode, cbUniLen);
	if (cbRet == 0)	return HRESULT_FROM_WIN32(GetLastError());

	pwszUnicode[cbRet] = L'\0';
	return S_OK;
}

STDAPI DsoConvertToMBCSEx(LPCWSTR pwszUnicodeString, DWORD cbUniLen, LPSTR pszMbcsString, DWORD cbMbcsLen)
{
	DWORD cbRet;

	CHECK_NULL_RETURN(pszMbcsString,    E_POINTER);
	pszMbcsString[0] = '\0';

	CHECK_NULL_RETURN(pwszUnicodeString, E_POINTER);
	CHECK_NULL_RETURN(cbMbcsLen,         E_INVALIDARG);
	CHECK_NULL_RETURN(cbUniLen,          E_INVALIDARG);

	cbRet = WideCharToMultiByte(CP_ACP, 0, pwszUnicodeString, -1, pszMbcsString, cbMbcsLen, NULL, NULL);
	if (cbRet == 0)	return HRESULT_FROM_WIN32(GetLastError());

	pszMbcsString[cbRet] = '\0';
	return S_OK;
}

///////////////////////////////////////////////////////////////////////////////////
// DsoConvertToBSTR
//
//  Takes a MBCS string and returns a BSTR. NULL is returned if the function fails
//  or the string is empty.
//
STDAPI_(BSTR) DsoConvertToBSTR(LPCSTR pszMbcsString)
{
	BSTR bstr = NULL;
	UINT cblen, cbnew;

	if (pszMbcsString)
	{
		cblen = lstrlen(pszMbcsString);
		if ((cblen > 0) && (*pszMbcsString != '\0'))
		{
			cbnew = ((cblen + 1) * sizeof(WCHAR));
			LPWSTR pwsz = (LPWSTR)MemAlloc(cbnew);
			if (pwsz) 
			{
				if (SUCCEEDED(DsoConvertToUnicodeEx(pszMbcsString, cblen, pwsz, cbnew)))
					bstr = SysAllocString(pwsz);

				MemFree(pwsz);
			}
		}
	}

	return bstr;
}

///////////////////////////////////////////////////////////////////////////////////
// DsoConvertToMBCS
//
//  Takes a WCHAR string and returns a LPSTR. Caller must free the string with MemFree.
//
STDAPI_(LPSTR) DsoConvertToMBCS(LPCWSTR pwszUnicodeString)
{
	LPSTR psz = NULL;
	UINT cblen, cbnew;

	if (pwszUnicodeString)
	{
		cblen = lstrlenW(pwszUnicodeString);
		cbnew = ((cblen + 1) * sizeof(WCHAR));
		psz = (LPSTR)MemAlloc(cbnew);
		if ((psz) && FAILED(DsoConvertToMBCSEx(pwszUnicodeString, cblen, psz, cbnew)))
		{
			MemFree(psz);
			psz = NULL;
		}
	}

	return psz;
}

///////////////////////////////////////////////////////////////////////////////////
// DsoCompareStrings
//
//  Calls CompareString API using Unicode version (if available on OS). Otherwise,
//  we have to thunk strings down to MBCS to compare. This is fairly inefficient for
//  Win9x systems that don't handle Unicode, but hey...this is only a sample.
//
STDAPI_(UINT) DsoCompareStrings(LPCWSTR pwsz1, LPCWSTR pwsz2)
{
	UINT iret;
	LCID lcid = GetThreadLocale();
	UINT cblen1, cblen2;

 // Check that valid parameters are passed and then contain somethimg...
	if ((pwsz1 == NULL) || ((cblen1 = lstrlenW(pwsz1)) == 0))
		return CSTR_LESS_THAN;

	if ((pwsz2 == NULL) || ((cblen2 = lstrlenW(pwsz2)) == 0))
		return CSTR_GREATER_THAN;

 // If the string is of the same size, then we do quick compare to test for
 // equality (this is slightly faster than calling the API, but only if we
 // expect the calls to find an equal match)...
	if (cblen1 == cblen2)
	{
		for (iret = 0; iret < cblen1; iret++)
		{
			if (pwsz1[iret] == pwsz2[iret])
				continue;

			if (((pwsz1[iret] >= 'A') && (pwsz1[iret] <= 'Z')) &&
				((pwsz1[iret] + ('a' - 'A')) == pwsz2[iret]))
				continue;

			if (((pwsz2[iret] >= 'A') && (pwsz2[iret] <= 'Z')) &&
				((pwsz2[iret] + ('a' - 'A')) == pwsz1[iret]))
				continue;

			break; // don't continue if we can't quickly match...
		}

		// If we made it all the way, then they are equal...
		if (iret == cblen1)
			return CSTR_EQUAL;
	}

 // Now ask the OS to check the strings and give us its read. (We prefer checking
 // in Unicode since this is faster and we may have strings that can't be thunked
 // down to the local ANSI code page)...
	if ((v_pfnCompareStringW) || (FEnsureProcAddresses() && v_pfnCompareStringW))
	{
		iret = (v_pfnCompareStringW)(lcid, 
			NORM_IGNORECASE | NORM_IGNOREWIDTH,	pwsz1, cblen1, pwsz2, cblen2);
	}
	else
	{
	 // If we are on Win9x, we don't have much of choice (thunk the call)...
		LPSTR psz1 = DsoConvertToMBCS(pwsz1);
		LPSTR psz2 = DsoConvertToMBCS(pwsz2);
		iret = CompareString(lcid, NORM_IGNORECASE,	psz1, -1, psz2, -1);
		MemFree(psz2);
		MemFree(psz1);
	}

	return iret;
}

///////////////////////////////////////////////////////////////////////////////////
// GetFormattedSize
//
//  Formats the DWORDLONG value to a string for display.
//
STDAPI_(LPSTR) GetFormattedSize(DWORDLONG dwlSize)
{
	LPSTR pszret = new CHAR[150];
    UINT i, wInt, wLen, wDec;

	ASSERT(pszret); CHECK_NULL_RETURN(pszret, NULL);

    if (dwlSize < 1000L)
    {
		wsprintf(pszret, "%d bytes", (DWORD)dwlSize);
		return pszret;
    }

    for (i = 1; dwlSize >= 1000L * 1024L; dwlSize /= 1024, i++)
        ; /* do nothing */

    wInt = (UINT)dwlSize / 1024;
	wLen = wsprintf(pszret, "%d", wInt);

    if (wLen < 3)
    {
        wDec = ((UINT) dwlSize - wInt * 1024L) * 1000 / 1024;
        wDec /= 10;
        if (wLen == 2)
            wDec /= 10;

        wLen += wsprintf(&pszret[wLen], ".%d", wDec);
    }
	
	lstrcpy(&pszret[wLen], ((i < 2) ? " KB" : (i < 3) ? " MB" : " GB"));
	return pszret;
}

///////////////////////////////////////////////////////////////////////////////////
// GetFormattedDate
//
//  Formats a FILETIME value to a string for display. We actually cheat a little
//  and let OLEAUT do all the work casting it into the right format.
//
STDAPI_(LPSTR) GetFormattedDate(FILETIME ftDate)
{
	LPSTR      pszret = NULL;
	DATE       dtDate;
	FILETIME   lft;
	SYSTEMTIME lst;
	FILETIME*  pft = &(ftDate);

	if ((ftDate.dwLowDateTime != 0) || (ftDate.dwHighDateTime != 0))
	{
		if (FileTimeToLocalFileTime(pft, &lft))
			pft = &lft;

		if (FileTimeToSystemTime(pft, &lst) && 
			SystemTimeToVariantTime(&lst, &dtDate))
		{
			BSTR bstr;
			if (SUCCEEDED(VarBstrFromDate(dtDate, GetThreadLocale(), 0, &bstr)))
			{
				pszret = DsoConvertToMBCS(bstr);
				SysFreeString(bstr);
			}
		}
	}

	return pszret;
}

///////////////////////////////////////////////////////////////////////////////////
// DsoOpenLocalFile
//
//  Returns a file handle for the given file. This wraps CreateFile API for a 
//  Unicode path string on systems that support it.
//
STDAPI DsoOpenLocalFile(LPCWSTR pwzFilePath, DWORD dwAccess, DWORD dwShareMode, DWORD dwCreate, HANDLE* phFile)
{
 // Make sure they pass a *handle...
    CHECK_NULL_RETURN(phFile,  E_POINTER);
    *phFile = INVALID_HANDLE_VALUE;

    CHECK_NULL_RETURN(pwzFilePath,  E_INVALIDARG);

	if ((v_pfnCreateFileW) || (FEnsureProcAddresses() && (v_pfnCreateFileW)))
	{
		*phFile = (v_pfnCreateFileW)(pwzFilePath, dwAccess, dwShareMode, NULL, dwCreate, FILE_ATTRIBUTE_NORMAL, NULL);
	}
	else
	{	// On Win9x we need to use ANSI to file system...
		LPSTR psz = DsoConvertToMBCS(pwzFilePath);
		if (psz) *phFile = CreateFile(psz, dwAccess, dwShareMode, NULL, dwCreate, FILE_ATTRIBUTE_NORMAL, NULL);
		MemFree(psz);
	}

    return ((*phFile != INVALID_HANDLE_VALUE) ? S_OK : (0x80070000|GetLastError()));
}

///////////////////////////////////////////////////////////////////////////////////
// DsoGetFileFromUser
//
//  Displays the Open/Save dialog using Unicode version if available. Returns the
//  path as a unicode BSTR regardless of OS.
//
STDAPI DsoGetFileFromUser(HWND hwndOwner, LPCWSTR pwzTitle, DWORD dwFlags, LPCWSTR pwszCurrentItem, BOOL fShowSave, BSTR *pbstrFile)
{
	BYTE buffer[MAX_PATH * sizeof(WCHAR)];
	BOOL fSuccess;
	DWORD dw;

 // Make sure they pass a *bstr...
    CHECK_NULL_RETURN(pbstrFile,  E_POINTER);
    *pbstrFile = NULL;

	buffer[0] = 0; buffer[1] = 0;

 // See if we have Unicode function to call. If so, we use OPENFILENAMEW and 
 // get the file path in Unicode, returned as BSTR...
	if ((v_pfnGetOpenFileNameW) || 
		(FEnsureProcAddresses() && (v_pfnGetOpenFileNameW)))
	{
		OPENFILENAMEW ofnw;
		memset(&ofnw,  0,   sizeof(OPENFILENAMEW));
	    ofnw.lStructSize  = sizeof(OPENFILENAMEW);
	    ofnw.hwndOwner    = hwndOwner;
	    ofnw.lpstrFilter  = L"All Files\0*.*\0";
	    ofnw.nFilterIndex = 1;
	    ofnw.lpstrTitle   = pwzTitle;
	    ofnw.lpstrFile    = (LPWSTR)&buffer[0];
	    ofnw.nMaxFile     = MAX_PATH;
	    ofnw.Flags        = dwFlags;

		if (pwszCurrentItem)
		{
			dw = lstrlenW(pwszCurrentItem);
			if ((dw) && (dw < MAX_PATH))
			{
				memcpy(ofnw.lpstrFile, pwszCurrentItem,  dw * sizeof(WCHAR));
				ofnw.lpstrFile[dw] = L'\0';
			}
		}

		if ((fShowSave) && (v_pfnGetSaveFileNameW))
			fSuccess = (v_pfnGetSaveFileNameW)(&ofnw);
		else
			fSuccess = (v_pfnGetOpenFileNameW)(&ofnw);

		if (fSuccess)
			*pbstrFile = SysAllocString((LPWSTR)&buffer[0]);
	}
	else
	{ // If not, then we use OPENFILENAMEA and thunk down our params to
	  // the MBCS of the system, and then thunk back the Unicode for the return...
		OPENFILENAMEA ofn;
		memset(&ofn,  0,   sizeof(OPENFILENAMEA));
	    ofn.lStructSize  = sizeof(OPENFILENAMEA);
	    ofn.hwndOwner    = hwndOwner;
	    ofn.lpstrFilter  = "All Files\0*.*\0";
	    ofn.nFilterIndex = 1;
	    ofn.lpstrTitle   = DsoConvertToMBCS(pwzTitle);
	    ofn.lpstrFile    = (LPSTR)&buffer[0];
	    ofn.nMaxFile     = MAX_PATH;
	    ofn.Flags        = dwFlags;

		if (pwszCurrentItem)
		{
			DsoConvertToMBCSEx(pwszCurrentItem, lstrlenW(pwszCurrentItem), (LPSTR)&buffer[0], MAX_PATH);
		}

		if (fShowSave)
			fSuccess = GetSaveFileName(&ofn);
		else
			fSuccess = GetOpenFileName(&ofn);

		if (fSuccess)
			*pbstrFile = DsoConvertToBSTR((LPCSTR)&buffer[0]);

	}

 // If we got a string, then success. All other errors (even user cancel) should
 // be treated as a general failure (feel free to change this for more full function).
    return ((*pbstrFile == NULL) ? E_FAIL : S_OK);
}


///////////////////////////////////////////////////////////////////////////////////
// URL Helpers
//
//
STDAPI_(BOOL) LooksLikeHTTP(LPCWSTR pwsz)
{
	return ((pwsz) && ((*pwsz == L'H') || (*pwsz == L'h')) &&
		((*(pwsz + 1) == L'T') || (*(pwsz + 1) == L't')) &&
		((*(pwsz + 2) == L'T') || (*(pwsz + 2) == L't')) &&
		((*(pwsz + 3) == L'P') || (*(pwsz + 3) == L'p')) &&
		((*(pwsz + 4) == L':') || (((*(pwsz + 4) == L'S') || (*(pwsz + 4) == L's')) && (*(pwsz + 5) == L':'))));
}

///////////////////////////////////////////////////////////////////////////////////
// ConstructUrlForFile
//
//  We combine two strings together and make a single URL path. This code is a bit
//  hacky. We should use WinInet here, but the sample assumes some fairly simple
//  URLs that make this OK in most cases. Good enough for a sample.
//
STDAPI_(LPWSTR) ConstructUrlForFile(LPCWSTR pwszUrlParent, LPCWSTR pwszFileName)
{
	LPWSTR pwsz, pwszT;
	UINT cblen1 = 0;
	UINT cblen2 = 0;

	CHECK_NULL_RETURN(pwszUrlParent, NULL);
	CHECK_NULL_RETURN(pwszFileName, NULL);

    if ((pwszUrlParent == NULL) || 
        ((cblen1 = lstrlenW(pwszUrlParent)) < 4) ||
        (!LooksLikeHTTP(pwszUrlParent)))
        return NULL;

    if ((pwszFileName == NULL) ||
        ((cblen2 = lstrlenW(pwszFileName)) < 1))
        return NULL;

	pwsz = (LPWSTR)MemAlloc((cblen1 + cblen2 + 2) * sizeof(WCHAR));
	CHECK_NULL_RETURN(pwsz, NULL);

	memcpy(pwsz, pwszUrlParent, (cblen1 * sizeof(WCHAR)));
    if (pwsz[cblen1] != L'/')
        pwsz[cblen1++] = L'/';

    pwszT = (LPWSTR)&pwszFileName[cblen2];
    while ((pwszT != pwszFileName) && (*pwszT != L'\\'))
        pwszT--;

    if (*pwszT == L'\\') pwszT++;

    cblen2 = lstrlenW(pwszT);
	memcpy(&pwsz[cblen1], pwszT, (cblen2 * sizeof(WCHAR)));

    pwsz[cblen1 + cblen2] = L'\0';
    return pwsz;
}


///////////////////////////////////////////////////////////////////////////////////
// DsoDisplayIPPError
//
//  Helper function to display error information from the provider. Fairly simple,
//  and not very safe with string handling. (You should fix this up before using in real app!)
//
STDAPI_(void) DsoDisplayIPPError(LPCSTR pszPrefix, HRESULT hr, LPCSTR pszTitle)
{
    IErrorInfo *perr;
    IMsdaIppErrorInfo *pipperr;
    IErrorRecords *perrec;
    CHAR szMessage[MSDAIPP_MAX_URL_LENGTH];
    BSTR bstrIPPError = NULL;
    BSTR bstrError = NULL;
    HRESULT hrInternal = 0;

 // Ask COM for error info and let's see what is set there. We should be asking
 // the provider if it supports this (ISupportErrorInfo) but we know that MSDAIPP
 // does and therefore are not worrying about it...
    if (SUCCEEDED(GetErrorInfo(0, &perr)) && (perr))
    {
     // Get the description from COM ErrorInfo...
        perr->GetDescription(&bstrError);

     // The try to get IPP 2.0 ErrorInfo object and get its description, and the internal
     // error value to output to user...
        if (SUCCEEDED(perr->QueryInterface(IID_IErrorRecords, (void**)&perrec)) && (perrec))
        {
            DWORD i, dw, cnt = 0; 
            perrec->GetRecordCount(&cnt);

            for (i = 0; i < cnt; i++)
            {
                 if (SUCCEEDED(perrec->GetCustomErrorObject(i, 
                     IID_IMsdaIppErrorInfo, (IUnknown**)&pipperr)) && (pipperr))
                 {

                    GUID guidInternal;
                    pipperr->GetMsdaIppErrorInfo(&hrInternal, &dw, &guidInternal);
                    pipperr->GetDescription(&bstrIPPError);
                    pipperr->Release();
                    break;
                 }
            }
            perrec->Release();
        }

        perr->Release();
    }

 // Construct the error message based on what info we get...
    if ((bstrError) && (bstrIPPError))
    {
        wsprintf(szMessage, "%s\r\n\r\n (0x%X) %S\r\n\r\n Internal (0x%X): %S", 
            pszPrefix, hr, ((*bstrError) ? bstrError : L"MSDAIPP Error"),
            hrInternal, ((*bstrError) ? bstrIPPError : L"MSDAIPP Error"));

        SysFreeString(bstrError);
        SysFreeString(bstrIPPError);

    }
    else if (bstrError)
    {
        wsprintf(szMessage, "%s\r\n\r\n (0x%X) %S", 
            pszPrefix, hr,  ((*bstrError) ? bstrError : L"MSDAIPP Error"));

        SysFreeString(bstrError);
    }
    else
    {
        wsprintf(szMessage, "%s (hr == 0x%X)", pszPrefix, hr);
    }

 // Then display the error...
    MessageBox(g_theApp->GetMainWindow(), szMessage,
        ((pszTitle) ? pszTitle : "OWebFolder Error"), MB_ICONHAND);

    return; //done.
}


////////////////////////////////////////////////////////////////////////
// Memory Allocation -- We use our own heap
// 
//
static HANDLE v_hAllocHeap   = NULL;   

STDAPI_(LPVOID) MemAlloc(DWORD cbSize)
{
	if (NULL == v_hAllocHeap)
	{
		if (NULL == (v_hAllocHeap = HeapCreate(0, 2048, 0)))
			return NULL;
	}
	return HeapAlloc(v_hAllocHeap, HEAP_ZERO_MEMORY, cbSize);
}

STDAPI_(void) MemFree(LPVOID ptr)
{
	ASSERT(v_hAllocHeap);
	if (ptr) HeapFree(v_hAllocHeap, 0, ptr);
}

void* _cdecl operator new(size_t size){return MemAlloc(size);}
void _cdecl operator delete(void *lpv){MemFree(lpv);}
int __cdecl _purecall(){__asm{int 3}; return 0;}
